package eerbaar.client.internal.exp;

import java.util.Stack;

import eerbaar.client.exp.ExpException;
import eerbaar.client.exp.ICmdFactory;
import eerbaar.client.exp.IEvaluator;
import eerbaar.client.exp.IExp;
import eerbaar.client.exp.INounFactory;
import eerbaar.client.exp.IOperatorFactory;
import eerbaar.client.exp.IScope;
import eerbaar.client.exp.NullCmdFactory;
import eerbaar.client.exp.Numeric;
import eerbaar.client.exp.Token;
import eerbaar.client.exp.TokenIterator;
import eerbaar.shared.tree.INode;
import eerbaar.shared.tree.NodeCmdFactory;
import eerbaar.shared.tree.NodeFactory;

public class Evaluator implements IEvaluator {

    private final static String EVALUATOR = "evaluator";
    private final ICmdFactory factory;
    private final INounFactory nounFactory;
    private final IOperatorFactory operatorFactory;
    private final INode root;
    private INode myNode;
    private IExp syntaxTree;

    public Evaluator(INode root, ICmdFactory factory,
            IOperatorFactory operatorFactory, INounFactory nounFactory) {

        this.factory = factory;
        this.operatorFactory = operatorFactory;
        this.root = root;
        this.nounFactory = nounFactory;
        getMyNode();
    }

    public Evaluator(IOperatorFactory f1, INounFactory f2) {

        this.operatorFactory = f1;
        this.nounFactory = f2;
        this.factory = new NullCmdFactory();
        this.root = NodeFactory.createRoot();
        getMyNode();
    }

    @Override
    public Object calcDestination(IScope scope) {
        assert (this.syntaxTree != null);

        scope.load(this.myNode);
        Object result = this.syntaxTree.calcDestination(scope);
        scope.save(this.myNode);

        return result;
    }

    private INode getMyNode() {

        this.myNode = NodeCmdFactory.findChild(this.root, EVALUATOR);
        if (this.myNode == null) {
            this.myNode = NodeFactory.create(EVALUATOR);
            NodeCmdFactory.add(this.root, this.myNode);
        }

        return this.myNode;
    }

    @Override
    public IScope interpret(IScope inScope) throws ExpException {
        assert (this.syntaxTree != null);

        IScope scope = inScope;

        scope.load(this.myNode);
        scope = this.syntaxTree.interpret(scope);
        scope.save(this.myNode);

        return scope;
    }

    @Override
    public boolean isInteresting(String elementName) {

        return this.syntaxTree.isInteresting(elementName);
    }

    @Override
    public IExp parse(TokenIterator inIterator) {

        TokenIterator iterator = inIterator;

        Stack<IExp> stack = new Stack<IExp>();

        while (iterator.hasNext()) {

            Token token = iterator.next();
            if (Numeric.isNumeric(token.toString())) {

                Numeric numeric = new Numeric(token.toString());
                stack.push(numeric);
            } else if (token.equals("(")) {

                Bracket bracket = new Bracket(this.root, this.factory,
                        this.operatorFactory, this.nounFactory);
                bracket.parse(iterator);
                stack.push(bracket);
            } else if (token.equals(")")) {

                break;
            } else if (this.operatorFactory.isOperator(token.toString())) {

                IExp[] array = stack.toArray(new IExp[] {});
                IExp operator = this.operatorFactory.create(array,
                        token.toString());
                stack.clear();
                stack.push(operator);
            } else if (this.nounFactory.isNoun(token.toString())) {

                IExp noun = this.nounFactory.create(token.toString());
                if (noun != null) {
                    stack.push(noun);
                }
            } else {

                IExp cmd = this.factory.getCmd(token.toString());
                if (cmd == null) {
                    boolean isLearned = this.factory.isLearnedCmd(token
                            .toString());
                    if (isLearned) {
                        Learned learned = new Learned(this.root, this.factory,
                                this.operatorFactory, this.nounFactory);
                        TokenIterator i = new TokenIterator(token);
                        learned.parse(i);
                        cmd = learned;
                    }
                }

                if (cmd != null) {
                    stack.push(cmd);
                }
            }
        }

        if (stack.size() > 0) {
            this.syntaxTree = stack.pop();
        }

        if (this.syntaxTree != null) {
            return this;
        }

        return null;
    }

    @Override
    public void populateValueArea(IScope scope) {

        scope.load(this.myNode);
        this.syntaxTree.populateValueArea(scope);
        scope.save(this.myNode);
    }

    @Override
    public String toString() {

        return "eval(" + this.syntaxTree + ")";
    }

    @Override
    public void updateArea(IScope scope) {

        scope.load(this.myNode);
        this.syntaxTree.updateArea(scope);
        scope.save(this.myNode);
    }

}
